#include "nosal/log/logging.h"
#include "nosal/log/logger.h"
#include "nosal/log/log_stream.h"
#include "nosal/log/internal/log_manager.h"
//#include "nosal/log/internal/utility.h"

#include "gtest/gtest.h"

using namespace ::testing;
using namespace netaos::nosal::log;
using LogManager = netaos::nosal::log::internal::LogManager;

namespace
{
constexpr char ctxId[]{"CTXT"};
constexpr char ctxDescr[]{"Context description"};
}  // namespace

struct LogStreamParamTest : TestWithParam<LogLevel>
{
    void SetUp() override
    {
    }
    void TearDown() override
    {
    }

    LogManager logManager;
};

INSTANTIATE_TEST_CASE_P(AllLogLevelsExceptOff,
    LogStreamParamTest,
    Values(LogLevel::kFatal, LogLevel::kError, LogLevel::kWarn, LogLevel::kInfo, LogLevel::kDebug, LogLevel::kVerbose)
);

TEST_P(LogStreamParamTest, shouldStartLogWriteWhenIsEnabled)
{
    auto& logger = logManager.CreateLogContext(ctxId, ctxDescr, LogLevel::kVerbose);

    LogStream{GetParam(), logger};
}


struct LogStreamTestBase : Test
{
    LogStreamTestBase()
    {
    }

    ~LogStreamTestBase()
    {
    }

    void expectContextCreated()
    {
    }

    void streamAnything(LogStream& stream)
    {
        stream << static_cast<uint32_t>(0U);
    }

    LogManager logManager;
};

TEST_F(LogStreamTestBase, shouldNotFinishWriteOnDestructionWhenNothingStreamed)
{
    auto& logger = logManager.CreateLogContext(ctxId, ctxDescr, LogLevel::kVerbose);

    {
        LogStream stream{LogLevel::kDebug, logger};
    }
}

TEST_F(LogStreamTestBase, shouldFinishWriteOnDestructionWhenAnythingStreamed)
{
    auto& logger = logManager.CreateLogContext(ctxId, ctxDescr, LogLevel::kVerbose);

    {
        LogStream stream{LogLevel::kDebug, logger};
        streamAnything(stream);

    }
}

struct LogStreamTest : LogStreamTestBase
{
    Logger& logger{logManager.CreateLogContext(ctxId, ctxDescr, LogLevel::kVerbose)};
    LogStream stream{LogLevel::kDebug, logger};
};

TEST_F(LogStreamTest, shouldStreamBool)
{
    stream << true << false;
}

TEST_F(LogStreamTest, shouldStreamUint8)
{
    stream << static_cast<uint8_t>(14U);
}

TEST_F(LogStreamTest, shouldStreamUint16)
{
    stream << static_cast<uint16_t>(25U);
}

TEST_F(LogStreamTest, shouldStreamUint32)
{
    stream << static_cast<uint32_t>(2U);
}

TEST_F(LogStreamTest, shouldStreamUint64)
{
    stream << static_cast<uint64_t>(138U);
}

TEST_F(LogStreamTest, shouldStreamInt8)
{
    constexpr int8_t value{-52};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamInt16)
{
    constexpr int16_t value{-8493};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamInt32)
{
    constexpr int32_t value{-463820191};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamInt64)
{
    constexpr int64_t value{-4887320110119};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamUnsignedShortInt)
{
    constexpr unsigned short int value{463};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamShortInt)
{
    constexpr short int value{1764};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamUnsignedInt)
{
    constexpr unsigned int value{7549};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamInt)
{
    constexpr int value{-339121};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamFloat)
{
    stream << 0.027f;
}

TEST_F(LogStreamTest, shouldStreamDouble)
{
    stream << 17.2154;
}

TEST_F(LogStreamTest, shouldStreamCharString)
{
    stream << "some chars";
}
#if 0
TEST_F(LogStreamTest, shouldStreamStdString)
{
    const std::string str{"some string"};
    stream << str;
}
#endif
TEST_F(LogStreamTest, shouldStreamLongLong)
{
    constexpr long long value{-14LL};
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamUnsignedLongLong)
{
    const unsigned long long value{15ULL};
    stream << value;
}

#if 0
TEST_F(LogStreamTest, shouldStreamLogHex8)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint8_formatted(NotNull(), 87, DLT_FORMAT_HEX8))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogHex8{87};
}

TEST_F(LogStreamTest, shouldStreamLogHex16)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint16_formatted(NotNull(), 0xad12, DLT_FORMAT_HEX16))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogHex16{0xad12};
}

TEST_F(LogStreamTest, shouldStreamLogHex32)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint32_formatted(NotNull(), 0xf4121, DLT_FORMAT_HEX32))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogHex32{0xf4121};
}

TEST_F(LogStreamTest, shouldStreamLogHex64)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint64_formatted(NotNull(), 0x56fae211029, DLT_FORMAT_HEX64))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogHex64{0x56fae211029};
}

TEST_F(LogStreamTest, shouldStreamLogBin8)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint8_formatted(NotNull(), 12, DLT_FORMAT_BIN8))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogBin8{12};
}

TEST_F(LogStreamTest, shouldStreamLogBin16)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint16_formatted(NotNull(), 13, DLT_FORMAT_BIN16))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogBin16{13};
}

TEST_F(LogStreamTest, shouldStreamLogBin32)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint32_formatted(NotNull(), 2128483543, DLT_FORMAT_DEFAULT))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogBin32{2128483543};
}

TEST_F(LogStreamTest, shouldStreamLogBin64)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint64_formatted(NotNull(), 9126372046884755804U, DLT_FORMAT_DEFAULT))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << LogBin64{9126372046884755804U};
}

TEST_F(LogStreamTest, shouldStreamLong)
{
    constexpr long value{-8652L};
    if (sizeof(long) == 4) {
        EXPECT_CALL(dltMock, dlt_user_log_write_int32(NotNull(), value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    if (sizeof(long) == 8) {
        EXPECT_CALL(dltMock, dlt_user_log_write_int64(NotNull(), value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    stream << value;
}

TEST_F(LogStreamTest, shouldStreamUnsignedLong)
{
    constexpr uint32_t value{43190U};
    if (sizeof(unsigned long) == 4) {
        EXPECT_CALL(dltMock, dlt_user_log_write_uint32(NotNull(), value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    if (sizeof(unsigned long) == 8) {
        EXPECT_CALL(dltMock, dlt_user_log_write_uint64(NotNull(), value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    stream << static_cast<unsigned long>(value);
}

TEST_F(LogStreamTest, shouldStreamAddress)
{
    if (sizeof(std::uintptr_t) == 4) {
        EXPECT_CALL(dltMock,
            dlt_user_log_write_uint32_formatted(NotNull(), reinterpret_cast<std::uintptr_t>(&stream), DLT_FORMAT_HEX32))
            .WillOnce(Return(DLT_RETURN_TRUE));
    }
    if (sizeof(std::uintptr_t) == 8) {
        EXPECT_CALL(dltMock,
            dlt_user_log_write_uint64_formatted(NotNull(), reinterpret_cast<std::uintptr_t>(&stream), DLT_FORMAT_HEX64))
            .WillOnce(Return(DLT_RETURN_TRUE));
    }
    stream << &stream;
}

TEST_F(LogStreamTest, shouldStreamEmptySpan)
{
    nosal::core::Vector<nosal::core::Byte> vec;
    nosal::core::Span<nosal::core::Byte> empty(vec);
    EXPECT_CALL(dltMock, dlt_user_log_write_raw(NotNull(), nullptr, 0)).WillOnce(Return(DLT_RETURN_TRUE));
    stream << empty;
}

TEST_F(LogStreamTest, shouldStreamSpan)
{
    nosal::core::Vector<nosal::core::Byte> vec{nosal::core::Byte(0xaa), nosal::core::Byte(0xbb), nosal::core::Byte(0xcc)};
    nosal::core::Span<nosal::core::Byte> span(vec);
    EXPECT_CALL(dltMock, dlt_user_log_write_raw(NotNull(), span.data(), span.size())).WillOnce(Return(DLT_RETURN_TRUE));
    stream << span;
}

TEST_F(LogStreamTest, shouldStreamErrorCode)
{
    const nosal::core::ErrorCode error{nosal::core::CoreErrc::kInvalidArgument, 81};
    const char* expectedStr{"Core:22:Invalid argument"};

    EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string(NotNull(), StrEq(expectedStr)))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << error;
}

TEST_F(LogStreamTest, shouldStreamInstanceSpecifier)
{
    const char instanceSpecifierStr[]{"instance/path"};
    const nosal::core::InstanceSpecifier instanceSpecifier{instanceSpecifierStr};

    EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string(NotNull(), StrEq(instanceSpecifierStr)))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << instanceSpecifier;
}

TEST_F(LogStreamTest, shouldNotStreamWithLocationWhenAnythingStreamed)
{
    streamAnything(stream);

    const nosal::core::StringView file{"file name"};
    constexpr int line{98};

    stream.WithLocation(file, line);
}

TEST_F(LogStreamTest, shouldStreamWithLocationWhenNothingStreamed)
{
    const nosal::core::StringView file{"file name"};
    constexpr int line{165};

    InSequence seq;
    EXPECT_CALL(dltMock, dlt_user_log_write_sized_utf8_string(NotNull(), StrEq(file.data()), file.size()))
        .WillOnce(Return(DLT_RETURN_TRUE));
    EXPECT_CALL(dltMock, dlt_user_log_write_int32(NotNull(), line)).WillOnce(Return(DLT_RETURN_TRUE));

    stream.WithLocation(file, line);
}

TEST_F(LogStreamTest, shouldStreamMultilineMessage)
{
    const uint16_t number{124};
    const int32_t negative_number{-6981};
    const auto first_str{"first token"};
    const auto second_str{"second token"};

    InSequence seq;
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_uint16(_, number)).WillOnce(Return(DLT_RETURN_TRUE));
        EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string(_, StrEq(first_str))).WillOnce(Return(DLT_RETURN_TRUE));
        EXPECT_CALL(dltMock, dlt_user_log_write_int32(_, negative_number)).WillOnce(Return(DLT_RETURN_TRUE));
        EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string(_, StrEq(second_str))).WillOnce(Return(DLT_RETURN_TRUE));

        stream << number << first_str << negative_number << second_str;
    }
}

TEST_F(LogStreamTest, shouldNotFlushWhenNothingStreamed)
{
    stream.Flush();
}

TEST_F(LogStreamTest, shouldFlushWhenAnythingStreamed)
{
    streamAnything(stream);

    InSequence seq;
    EXPECT_CALL(dltMock, dlt_user_log_write_finish(_));
    EXPECT_CALL(dltMock, dlt_user_log_write_start(_, _, _)).WillOnce(Return(DLT_RETURN_TRUE));

    stream.Flush();
}

TEST_F(LogStreamTest, shouldStreamAfterFlush)
{
    streamAnything(stream);

    InSequence seq;
    EXPECT_CALL(dltMock, dlt_user_log_write_finish(_));
    EXPECT_CALL(dltMock, dlt_user_log_write_start(_, _, _)).WillOnce(Return(DLT_RETURN_TRUE));
    stream.Flush();

    EXPECT_CALL(dltMock, dlt_user_log_write_int32(_, _)).WillOnce(Return(DLT_RETURN_TRUE));
    EXPECT_CALL(dltMock, dlt_user_log_write_float64(_, _)).WillOnce(Return(DLT_RETURN_TRUE));
    stream << 37 << 4.5;
}

TEST_F(LogStreamTest, shouldNotStreamAfterFlushFailed)
{
    streamAnything(stream);

    EXPECT_CALL(dltMock, dlt_user_log_write_finish(_));
    EXPECT_CALL(dltMock, dlt_user_log_write_start(_, _, _)).WillOnce(Return(DLT_RETURN_ERROR));
    stream.Flush();

    stream << 37 << 4.5;
}

struct LogStreamStdChronoTest : LogStreamTest
{
    void expectWriteString(const char* str)
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string(NotNull(), StrEq(str))).WillOnce(Return(DLT_RETURN_TRUE));
    }
};

TEST_F(LogStreamStdChronoTest, shouldStreamNanoSeconds)
{
    expectWriteString("356ns");
    stream << std::chrono::nanoseconds(356);
}

TEST_F(LogStreamStdChronoTest, shouldStreamMicroSeconds)
{
    expectWriteString("109us");
    stream << std::chrono::microseconds(109);
}

TEST_F(LogStreamStdChronoTest, shouldStreamMilliSeconds)
{
    expectWriteString("5ms");
    stream << std::chrono::milliseconds(5);
}

TEST_F(LogStreamStdChronoTest, shouldStreamSeconds)
{
    expectWriteString("21s");
    stream << std::chrono::seconds(21);
}

TEST_F(LogStreamStdChronoTest, shouldStreamCentiSeconds)
{
    expectWriteString("817cs");
    stream << std::chrono::duration<int, std::centi>(817);
}

TEST_F(LogStreamStdChronoTest, shouldStreamDeciSeconds)
{
    expectWriteString("5284ds");
    stream << std::chrono::duration<int, std::deci>(5284);
}

TEST_F(LogStreamStdChronoTest, shouldStreamMinutesExpressedInSecs)
{
    expectWriteString("31(60/1)s");
    stream << std::chrono::minutes(31);
}

TEST_F(LogStreamStdChronoTest, shouldStreamHoursExpressedInSecs)
{
    expectWriteString("24(3600/1)s");
    stream << std::chrono::hours(24);
}

namespace
{
struct LogLevelParams
{
    LogLevel inputLogLevel;
    std::string logLevelStr;
};
}  // namespace

struct LogLevelStreamTest
    : LogStreamTest
    , WithParamInterface<LogLevelParams>
{ };

INSTANTIATE_TEST_CASE_P(LogLevelStreamTest,
    LogLevelStreamTest,
    Values(LogLevelParams{LogLevel::kOff, "Off"},
        LogLevelParams{LogLevel::kFatal, "Fatal"},
        LogLevelParams{LogLevel::kError, "Error"},
        LogLevelParams{LogLevel::kWarn, "Warn"},
        LogLevelParams{LogLevel::kInfo, "Info"},
        LogLevelParams{LogLevel::kDebug, "Debug"},
        LogLevelParams{LogLevel::kVerbose, "Verbose"},
        LogLevelParams{static_cast<LogLevel>(74), "74"}));

TEST_P(LogLevelStreamTest, shouldStreamLogLevelAsString)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string(NotNull(), StrEq(GetParam().logLevelStr)))
        .WillOnce(Return(DLT_RETURN_TRUE));
    stream << GetParam().inputLogLevel;
}

TEST_P(LogLevelStreamTest, shouldConvertLogLevelToExpectedString)
{
    const auto inputLogLevelStr = nosal::log::internal::to_string(GetParam().inputLogLevel);
    EXPECT_EQ(inputLogLevelStr, GetParam().logLevelStr);
}

struct TmpLogStreamTest : LogStreamTestBase
{
    Logger& getLogger()
    {
        return logManager.CreateLogContext(ctxId, ctxDescr, LogLevel::kVerbose);
    }
};

TEST_F(TmpLogStreamTest, shouldStreamUint32)
{
    EXPECT_CALL(dltMock, dlt_user_log_write_uint32(NotNull(), 67438)).WillOnce(Return(DLT_RETURN_TRUE));
    getLogger().LogInfo() << static_cast<uint32_t>(67438);
}

TEST_F(TmpLogStreamTest, shouldStreamStdString)
{
    const std::string str{"str"};
    EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string(_, StrEq(str.c_str()))).WillOnce(Return(DLT_RETURN_TRUE));
    getLogger().LogInfo() << str;
}

TEST_F(TmpLogStreamTest, shouldStreamLong)
{
    constexpr int32_t value{-381};
    if (sizeof(long) == 4) {
        EXPECT_CALL(dltMock, dlt_user_log_write_int32(_, value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    if (sizeof(long) == 8) {
        EXPECT_CALL(dltMock, dlt_user_log_write_int64(_, value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    getLogger().LogInfo() << static_cast<long>(value);
}

TEST_F(TmpLogStreamTest, shouldStreamUnsignedLong)
{
    constexpr uint32_t value{1628U};
    if (sizeof(unsigned long) == 4) {
        EXPECT_CALL(dltMock, dlt_user_log_write_uint32(_, value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    if (sizeof(unsigned long) == 8) {
        EXPECT_CALL(dltMock, dlt_user_log_write_uint64(_, value)).WillOnce(Return(DLT_RETURN_TRUE));
    }
    getLogger().LogInfo() << static_cast<unsigned long>(value);
}

TEST_F(TmpLogStreamTest, shouldStreamAddress)
{
    const uint32_t value{451};
    if (sizeof(std::uintptr_t) == 4) {
        EXPECT_CALL(
            dltMock, dlt_user_log_write_uint32_formatted(_, reinterpret_cast<std::uintptr_t>(&value), DLT_FORMAT_HEX32))
            .WillOnce(Return(DLT_RETURN_TRUE));
    }
    if (sizeof(std::uintptr_t) == 8) {
        EXPECT_CALL(
            dltMock, dlt_user_log_write_uint64_formatted(_, reinterpret_cast<std::uintptr_t>(&value), DLT_FORMAT_HEX64))
            .WillOnce(Return(DLT_RETURN_TRUE));
    }
    getLogger().LogInfo() << &value;
}

struct LogStreamDisabledTest : TestWithParam<DltReturnValue>
{
    LogStreamDisabledTest()
    {
        test::initDltMock(&dltMock);

        EXPECT_CALL(dltMock, dlt_register_context_ll_ts(_, _, _, _, _)).WillOnce(Return(DLT_RETURN_OK));
        EXPECT_CALL(dltMock, dlt_set_resend_timeout_atexit(_));
    }

    ~LogStreamDisabledTest()
    {
        test::cleanupDltMock();
    }

    void disableLogging()
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_start(_, _, _)).WillOnce(Return(GetParam()));
    }

    Logger& getLogger()
    {
        return logManager.CreateLogContext(ctxId, ctxDescr, LogLevel::kVerbose);
    }

    LogManager logManager;
    StrictMock<DltMock> dltMock;
};

INSTANTIATE_TEST_CASE_P(InvalidRetValueOnLogWriteStart,
    LogStreamDisabledTest,
    Values(DLT_RETURN_OK, DLT_RETURN_ERROR, DLT_RETURN_WRONG_PARAMETER, DLT_RETURN_LOGGING_DISABLED));

TEST_P(LogStreamDisabledTest, shouldNotWriteToDltWhenLoggingDisabled)
{
    disableLogging();

    LogStream stream{LogLevel::kDebug, getLogger()};

    uint8_t buffer[4] = {1, 2, 3, 4};
    nosal::core::Span<uint8_t> sp1(buffer);

    stream << true << static_cast<uint8_t>(81) << static_cast<int8_t>(-4) << static_cast<uint16_t>(1236)
           << static_cast<int16_t>(-53) << static_cast<uint32_t>(88612) << static_cast<int32_t>(-62329)
           << static_cast<uint64_t>(3288917) << static_cast<int64_t>(-723) << 21.54429f << 45.7 << "char str"
           << std::string{"std string"} << nosal::core::StringView{"string view"} << nosal::core::as_bytes(sp1)
           << LogHex8{87} << LogHex16{95} << LogHex32{0xae1f2} << LogHex64{0x5fedcc82} << 381ULL << -843LL
           << LogBin8{12} << LogBin16{13} << LogBin32{7738321} << LogBin64{884937149491} << LogLevel::kWarn
           << nosal::core::Span<const nosal::core::Byte>{}
           << nosal::core::ErrorCode{nosal::core::CoreErrc::kInvalidArgument, 0};
}

TEST_P(LogStreamDisabledTest, shouldNotWriteAnyArgToDltWhenLoggingDisabled)
{
    disableLogging();

    LogStream stream{LogLevel::kDebug, getLogger()};

    constexpr char name[]{"name"};
    constexpr char unit[]{"unit"};

    stream << Arg(true, name) << Arg(static_cast<uint8_t>(21), name, unit) << Arg(static_cast<int8_t>(-47), name, unit)
           << Arg(static_cast<uint16_t>(1392), name, unit) << Arg(static_cast<int16_t>(-8941), name, unit)
           << Arg(static_cast<uint32_t>(7845513), name, unit) << Arg(static_cast<int32_t>(-8001253), name, unit)
           << Arg(static_cast<uint64_t>(56732075), name, unit) << Arg(static_cast<int64_t>(-202931), name, unit)
           << Arg(491.11f, name, unit) << Arg(8721.787, name, unit) << Arg("char string", name)
           << Arg(nosal::core::StringView{"string view"}, name) << Arg(nosal::core::Span<const nosal::core::Byte>{}, name);
}

TEST_P(LogStreamDisabledTest, shouldNotFlushWhenLoggingDisabled)
{
    disableLogging();

    LogStream stream{LogLevel::kDebug, getLogger()};
    stream << 12;
    stream.Flush();
}

TEST_F(LogStreamDisabledTest, shouldNotTryToEnableLoggingWhenLogLevelIsOff)
{
    LogStream{LogLevel::kOff, getLogger()};
}

struct ArgLogStreamTest : LogStreamTest
{ };

TEST_F(ArgLogStreamTest, shouldStreamBool)
{
    {
        constexpr char name[]{"boolean name"};
        constexpr auto value{false};
        EXPECT_CALL(dltMock, dlt_user_log_write_bool_attr(NotNull(), value, StrEq(name)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        constexpr auto value{true};
        EXPECT_CALL(dltMock, dlt_user_log_write_bool_attr(NotNull(), value, nullptr)).WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamUint8)
{
    constexpr char name[]{"uint8 name"};
    {
        constexpr char unit[]{"uint8 unit"};
        constexpr uint8_t value{196U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint8_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        constexpr uint8_t value{75U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint8_attr(NotNull(), value, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        constexpr uint8_t value{205U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint8_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamUint16)
{
    constexpr char name[]{"uint16 name"};
    {
        constexpr char unit[]{"uint16 unit"};
        constexpr uint16_t value{45781U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint16_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        const uint16_t value{18437U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint16_attr(NotNull(), value, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        constexpr uint16_t value{3281U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint16_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamUint32)
{
    constexpr char name[]{"uint32 name"};
    {
        constexpr char unit[]{"uint32 unit"};
        static const uint32_t value{361736331U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint32_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        static const uint32_t value{87378102U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint32_attr(NotNull(), value, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        static const uint32_t value{510023823U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint32_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamUint64)
{
    constexpr char name[]{"uint64 name"};
    {
        constexpr char unit[]{"uint64 unit"};
        const auto value{std::numeric_limits<uint64_t>::max()};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint64_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        const uint64_t value{88190125213176U};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint64_attr(NotNull(), value, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        const volatile auto value{std::numeric_limits<uint64_t>::min()};
        EXPECT_CALL(dltMock, dlt_user_log_write_uint64_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamInt8)
{
    constexpr char name[]{"int8 name"};
    {
        constexpr char unit[]{"int8 unit"};
        const volatile int8_t value{104};
        EXPECT_CALL(dltMock, dlt_user_log_write_int8_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        const volatile int8_t value{-82};
        EXPECT_CALL(dltMock, dlt_user_log_write_int8_attr(NotNull(), value, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        const volatile int8_t value{73};
        EXPECT_CALL(dltMock, dlt_user_log_write_int8_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamInt16)
{
    constexpr char name[]{"int16 name"};
    {
        constexpr char unit[]{"int16 unit"};
        constexpr volatile int16_t value{12873};
        EXPECT_CALL(dltMock, dlt_user_log_write_int16_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_int16_attr(NotNull(), 28071, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(static_cast<int16_t>(28071), name);
    }
    {
        constexpr volatile int16_t value{-10936};
        EXPECT_CALL(dltMock, dlt_user_log_write_int16_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamInt32)
{
    constexpr char name[]{"int32 name"};
    {
        constexpr char unit[]{"int32 unit"};
        const volatile auto value{std::numeric_limits<int32_t>::min()};
        EXPECT_CALL(dltMock, dlt_user_log_write_int32_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        const int32_t value{1267839};
        EXPECT_CALL(dltMock, dlt_user_log_write_int32_attr(NotNull(), value, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        const int32_t value{-4901835};
        EXPECT_CALL(dltMock, dlt_user_log_write_int32_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamInt64)
{
    constexpr char name[]{"int64 name"};
    {
        constexpr char unit[]{"int64 uint"};
        constexpr auto value{std::numeric_limits<int64_t>::min()};
        EXPECT_CALL(dltMock, dlt_user_log_write_int64_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        constexpr int64_t value{1829530912882};
        EXPECT_CALL(dltMock, dlt_user_log_write_int64_attr(NotNull(), value, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name);
    }
    {
        constexpr int64_t value{-9101752};
        EXPECT_CALL(dltMock, dlt_user_log_write_int64_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamFloat)
{
    constexpr char name[]{"int32 name"};
    {
        constexpr char unit[]{"int32 unit"};
        EXPECT_CALL(dltMock, dlt_user_log_write_float32_attr(NotNull(), 349.35f, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(349.35f, name, unit);
    }
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_float32_attr(NotNull(), 51.076f, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(51.076f, name);
    }
    {
        constexpr auto value{-805.56f};
        EXPECT_CALL(dltMock, dlt_user_log_write_float32_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamDouble)
{
    constexpr char name[]{"int32 name"};
    {
        constexpr char unit[]{"int32 unit"};
        constexpr auto value{8756.4103};
        EXPECT_CALL(dltMock, dlt_user_log_write_float64_attr(NotNull(), value, StrEq(name), StrEq(unit)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value, name, unit);
    }
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_float64_attr(NotNull(), 8164.1, StrEq(name), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(8164.1, name);
    }
    {
        constexpr auto value{-19156.870};
        EXPECT_CALL(dltMock, dlt_user_log_write_float64_attr(NotNull(), value, nullptr, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(value);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamEmptyCharString)
{
    {
        constexpr char name[]{"const char empty string name"};
        constexpr char str[]{""};
        EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string_attr(NotNull(), str, StrEq(name)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str, name);
    }
    {
        const char* str{nullptr};
        EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string_attr(NotNull(), str, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamCharString)
{
    {
        constexpr char name[]{"const char string name"};
        constexpr char str[]{"some string"};
        EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string_attr(NotNull(), str, StrEq(name)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str, name);
    }
    {
        const char* str{"other string"};
        EXPECT_CALL(dltMock, dlt_user_log_write_utf8_string_attr(NotNull(), str, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamEmptyStringView)
{
    const nosal::core::StringView str{""};
    {
        const char* name{"empty string view name"};
        EXPECT_CALL(
            dltMock, dlt_user_log_write_sized_utf8_string_attr(NotNull(), StrEq(str.data()), str.size(), StrEq(name)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str, name);
    }
    {
        EXPECT_CALL(
            dltMock, dlt_user_log_write_sized_utf8_string_attr(NotNull(), StrEq(str.data()), str.size(), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamStringView)
{
    {
        const char* name{"string view name"};
        const nosal::core::StringView str{"some string view"};
        EXPECT_CALL(
            dltMock, dlt_user_log_write_sized_utf8_string_attr(NotNull(), StrEq(str.data()), str.size(), StrEq(name)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str, name);
    }
    {
        const nosal::core::StringView str{"other string view"};
        EXPECT_CALL(
            dltMock, dlt_user_log_write_sized_utf8_string_attr(NotNull(), StrEq(str.data()), str.size(), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(str);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamEmptySpan)
{
    nosal::core::Vector<nosal::core::Byte> vec;
    nosal::core::Span<const nosal::core::Byte> empty(vec);
    {
        const char* name{"empty span name"};
        EXPECT_CALL(dltMock, dlt_user_log_write_raw_attr(NotNull(), nullptr, 0, StrEq(name)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(empty, name);
    }
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_raw_attr(NotNull(), nullptr, 0, nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(empty);
    }
}

TEST_F(ArgLogStreamTest, shouldStreamSpan)
{
    nosal::core::Vector<nosal::core::Byte> vec{nosal::core::Byte(0x12), nosal::core::Byte(0x34), nosal::core::Byte(0x56)};
    nosal::core::Span<const nosal::core::Byte> span(vec);
    {
        const char* name{"span name"};
        EXPECT_CALL(dltMock, dlt_user_log_write_raw_attr(NotNull(), span.data(), span.size(), StrEq(name)))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(span, name);
    }
    {
        EXPECT_CALL(dltMock, dlt_user_log_write_raw_attr(NotNull(), span.data(), span.size(), nullptr))
            .WillOnce(Return(DLT_RETURN_TRUE));
        stream << Arg(span);
    }
}
#endif
